﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace CodeAngel.Domain.Framework
{
    #region IValueProperty

    /// <summary>
    /// Interface for a property that contains a single value/object.
    /// </summary>
    public interface IValueProperty : IProperty
    {
        /// <summary>
        /// Gets the value of this property.
        /// </summary>
        object GetValue();

        /// <summary>
        /// Determines if the identified value satisfies all rules assigned to this property.
        /// </summary>
        /// <Remarks>Returns True if it does; otherwise False.</Remarks>
        bool IsValueValid(object value);

        /// <summary>
        /// Indicates when the value of this property has changed.
        /// </summary>
        event ValueChangedEventHandler ValueChanged;

        /// <summary>
        /// Identifies if the value of this property is an Entity.
        /// </summary>
        bool ValueIsAnEntity { get; }

    }

    #endregion

    #region ValueProperty<T>

    /// <summary>
    /// Implementation of the IValueProperty interface.
    /// </summary>
    public class ValueProperty<T> : IValueProperty
    {
        public const int X_EQUAL_TO_Y = 0;
        public const int X_LESS_THAN_Y = -1;
        public const int X_GREATER_THAN_Y = 1;

        #region Constructors

        /// <summary>
        /// Creates a new property without any value (starts off as the default type for T).
        /// </summary>
        public ValueProperty(string name, string friendlyName, Entity parentEntity)
            : this(name, friendlyName, parentEntity, default(T))
        { }

        /// <summary>
        /// Creates a new property set with the identified value.
        /// </summary>
        public ValueProperty(string name, string friendlyName, Entity parentEntity, T value)
        {
            this.Name = name;
            this.FriendlyName = friendlyName;
            this.ParentEntity = parentEntity;
            this.ValidationRules = new RuleCollection();
            this._Value = value;
        }

        #endregion

        #region Properties
        
        /// <summary>
        /// Identifies the name of this property.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Identifies the name of this property that is appropriate to display to the user.
        /// </summary>
        public string FriendlyName { get; private set; }

        /// <summary>
        /// Identifies the parent entity that this property belongs to.
        /// </summary>
        public Entity ParentEntity { get; private set; }

        #endregion

        #region Value

        /// <summary>
        /// Gets or sets the value of this property.
        /// </summary>
        public T Value
        {
            get { return this._Value; }
            set
            {
                if (ValueProperty<T>.AreValuesDifferent(this, value))
                {
                    T PreviousValue = this._Value;
                    //this.RaiseValueChanging(PreviousValue, value);
                    this._Value = value;
                    this.RaiseValueChanged(PreviousValue, value);

                    // TODO: Don't exactly like that this will be calling a parent - seems to hide something
                    // that the parent should know about?  However, it is very convenient...
                    this.ParentEntity.RaisePropertyChanged(this.Name);
                }
            }
        }
        private T _Value;

        /// <summary>
        /// Gets the value of this property.
        /// </summary>
        /// <remarks>Enables access to the value of this property through the IProperty interface.</remarks>
        public object GetValue()
        {
            return this._Value;
        }

        #endregion

        #region ValueChanging & ValueChanged Events

        ///// <summary>
        ///// Indicates that our Value is about to change.
        ///// </summary>
        ///// <remarks>See the eventargs for previous and new values.</remarks>
        //public event ValueChangingEventHandler ValueChanging;
        //protected void RaiseValueChanging(object previousValue, object newValue)
        //{
        //    if (this.ValueChanging != null) this.ValueChanging(this, new ValueChangingEventArgs(previousValue, newValue));
        //}

        /// <summary>
        /// Indicates that our Value has changed.
        /// </summary>
        /// <remarks>See the eventargs for previous and new values.</remarks>
        public event ValueChangedEventHandler ValueChanged;
        protected void RaiseValueChanged(object previousValue, object newValue)
        {
            if (this.ValueChanged != null) this.ValueChanged(this, new ValueChangedEventArgs(previousValue, newValue));
        }

        #endregion

        #region Validation

        /// <summary>
        /// Identifies the business rules of this property that relate to whether our parent 
        /// entity should be allowed to be saved.
        /// </summary>
        public RuleCollection ValidationRules { get; private set; }

        /// <summary>
        /// Determines if the current value of this property satisfies all the ValuePropertyRule 
        /// rules assigned to this property.  If the value of this property is an Entity, then the 
        /// rules associated with that entity will also be evaluated for validity.
        /// </summary>
        public bool IsValid()
        {
            if (!this.IsValueValid(this._Value)) return false;

            if (this.ValueIsAnEntity)
            {
                var ValueAsEntity = this._Value as Entity;
                return ValueAsEntity.IsValid();
            }

            return true;
        }

        /// <summary>
        /// Determines if the identified value satisfies all the ValuePropertyRule rules assigned 
        /// to this property.
        /// </summary>
        public bool IsValueValid(object value)
        {
            foreach (ValuePropertyRule ThisRule in this.ValidationRules)
                if (!ThisRule.IsValueValid(value)) return false;
            return true;
        }

        /// <summary>
        /// Identifies any rules assigned to this property that are not valid based on its current value.
        /// </summary>
        public RuleCollection GetInvalidRules()
        {
            return this.GetInvalidRulesForValue(this._Value);
        }

        /// <summary>
        /// Identifies any rules assigned to this property that are not valid using the identified value.
        /// </summary>
        public RuleCollection GetInvalidRulesForValue(object value)
        {
            var InvalidRules = new RuleCollection();
            foreach (ValuePropertyRule ThisRule in this.ValidationRules)
                if (!ThisRule.IsValueValid(value)) InvalidRules.Add(ThisRule);
            return InvalidRules;
        }

        #endregion

        #region AreValuesDifferent

        /// <summary>
        /// Determines if the value of the identified ValueProperty is different from the other value.
        /// </summary>
        public static bool AreValuesDifferent(ValueProperty<T> thisValueProperty, T other)
        {
            //if (thisValueProperty.IsNull)
            //    return (!other.Equals(null));

            //else if (other.Equals(null))
            //    return true;

            //else

            if (thisValueProperty.Value == null)
            {
                return other != null;
            }
            else
            {
                return !thisValueProperty.Value.Equals(other);
            }
        }

        #endregion

        #region ValueIsAnEntity

        public bool ValueIsAnEntity
        {
            get { return this._Value != null && this._Value is Entity; }
        }

        #endregion
    }

    #endregion

    #region ValueChangingEventHandler & ValueChangingEventArgs

    public delegate void ValueChangingEventHandler(object sender, ValueChangingEventArgs e);

    /// <summary>
    /// Event args for a ValueChangingEventHandler.
    /// </summary>
    /// <remarks>
    /// Identifies the current value and proposed value before the value of a property is 
    /// about to change.
    /// </remarks>
    public class ValueChangingEventArgs : EventArgs
    {

        public ValueChangingEventArgs(object currentValue, object proposedValue)
        {
            this.CurrentValue = currentValue;
            this.ProposedValue = proposedValue;
        }

        public object CurrentValue { get; private set; }
        public object ProposedValue { get; private set; }

    }

    #endregion

    #region ValueChangedEventHandler & ValueChangedEventArgs

    public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);

    /// <summary>
    /// Event args for a ValueChangedEventHandler.
    /// </summary>
    /// <remarks>
    /// Identifies the previous value and new value for when the value of a property has changed.
    /// </remarks>
    public class ValueChangedEventArgs : EventArgs
    {

        public ValueChangedEventArgs(object previousValue, object newValue)
        {
            this.PreviousValue = previousValue;
            this.NewValue = newValue;
        }

        public object PreviousValue { get; private set; }
        public object NewValue { get; private set; }

    }

    #endregion
}
